#include "vcl_stl.h"

#include "bkbase.h"
#include "bstcp.h"
#include "bkipc.h"

#include "h_base.h"
#include "h_xml.h"
#include "h_conv.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

#include "simbat_plugin.h"

#include <dlfcn.h>
#include <sys/stat.h>

static const int COMM_TIME_OUT = 30000;

static char *SimbatBase;
static HString SyslogDir;
static HString JobNameDelimiter = ":";
static HSyslog Syslog;
static HString ShrDir;
static char ErrMsg[8192 + 1];
static SimbatPluginDbInitArg DbFunc;
static bool HasDbFunc = false;
static HString SystemName;
static int MyId = -1;                               // 本服务器ID（从0开始）
static HNodeRole MyRole = nrMaster;                 // 本服务器角色（MASTER/SLAVE/BACKUP）
static bool UploadStatus = false;
static int GatewayIdx = 0;                          // 当前连接的网关
static vector<HString> GatewayAddrVec;              // 网关地址（可以一个或者两个）

struct job_info
{
   HString FullName;
   bool PartFlag;
   map<HString, int> PartMap;

   job_info()
      : PartFlag(false)
   {}
};

map<HString, job_info> JobMap;

static int GatewayConnect(const char *System, char ErrStr[200 + 1]);
static int GatewayCountStatus(const char *System, int *Result, char ErrStr[200 + 1]);
static int GatewayClearStatus(const char *System, char ErrStr[200 + 1]);
static int GatewayWriteStatus(const char *System, int Idx, const char *RecType, const char *Record, char ErrStr[200 + 1]);
static int GatewayReadStatus(const char *System, int Idx, char *RecType, char *Record, char ErrStr[200 + 1]);
static int GatewayRewriteStatus(const char *System, int Idx, const char *RecType, const char *Record, char ErrStr[200 + 1]);

static int GatewayConnect(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|||connect|%s", SystemName.c_str());

   bool b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", "", TmpStr, true, ResultStr);

   if ( !b && GatewayAddrVec.size() > 1 )
   {
      GatewayIdx = 1 - GatewayIdx;
      AddrStr = GatewayAddrVec[GatewayIdx];
      b = CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", "", TmpStr, true, ResultStr);
   }

   if ( !b )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayCountStatus(const char *System, int *Result, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|||count|%s", System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      if ( GatewayAddrVec.size() > 1 )
      {
         GatewayIdx = 1 - GatewayIdx;
      }
      return 8;
   }

   *Result = StrToInt(ResultStr.c_str());
   return 0;
}

static int GatewayClearStatus(const char *System, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|||clear|%s", System);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, true, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      return 8;
   }

   return 0;
}

static int GatewayWriteStatus(const char *System, int Idx, const char *RecType, const char *Record, char ErrStr[200 + 1])
{
   return 0;
}

static int GatewayReadStatus(const char *System, int Idx, char *RecType, char *Record, char ErrStr[200 + 1])
{
   char TmpStr[4096 + 1];
   HString ResultStr;
   HString &AddrStr = GatewayAddrVec[GatewayIdx];

   snprintf(TmpStr, 4096, "X|||read|%s|%d", System, Idx);

   if ( !CallServer(Syslog, COMM_TIME_OUT, AddrStr, "DATABASE", System, TmpStr, false, ResultStr) )
   {
      snprintf(ErrStr, 200, "%s", ResultStr.c_str());
      if ( GatewayAddrVec.size() > 1 )
      {
         GatewayIdx = 1 - GatewayIdx;
      }
      return 8;
   }

   vector<HString> StrVec;

   SplitString(ResultStr.c_str(), '|', StrVec, true);

   if ( StrVec.size() < 2 )
   {
      snprintf(ErrStr, 200, "Invalid output %s", ResultStr.c_str());
      return 8;
   }

   BkStrNCpy(RecType, StrVec[0].c_str(), 200);
   BkStrNCpy(Record, StrVec[1].c_str(), 2000);
   return 0;
}

static int GatewayRewriteStatus(const char *System, int Idx, const char *RecType, const char *Record, char ErrStr[200 + 1])
{
   return 0;
}

static void LoadFromCfg(HXmlTree &Cfg)
{
   SyslogDir = EnvEvaluate(Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc"));
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));

   if ( !GetMyId(SimbatBase, MyId, MyRole, ErrMsg) )
   {
      Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
      return;
   }

   JobNameDelimiter = Cfg.GetValue("JOB_NAME_DELIMITER", ":");
   ShrDir = EnvEvaluate(Cfg.GetValue("SHR_DIR", "$SIMBAT_BASE/shr"));
   setenv("SIMBAT_SHR_DIR", ShrDir.c_str(), 1);

   {
      memset(&DbFunc, 0, sizeof(DbFunc));
      HString PluginFileName = EnvEvaluate(Cfg.GetValue("PLUGIN_DB_STATUS"));
      if ( PluginFileName != "" )
      {
         void *PluginHandle = dlopen(PluginFileName.c_str(), RTLD_NOW);
         if ( PluginHandle == NULL )
         {
            snprintf(ErrMsg, 8192, "dlopen(%s) fail, error = %s", PluginFileName.c_str(), dlerror());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
         else
         {
            SIMBAT_PLUGIN_DB_INIT Func = (SIMBAT_PLUGIN_DB_INIT)dlsym(PluginHandle, "SimbatPluginDbInit");
            if ( Func == NULL )
            {
               snprintf(ErrMsg, 8192, "dlsym(%s) fail, error = %s", "SimbatPluginDbInit", dlerror());
               Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
            }
            else
            {
               Func(&DbFunc);
               HasDbFunc = true;
            }
         }
      }
   }

   SystemName = Cfg.GetValue("SYSTEM_NAME", "simbat");

   {
      HString Str = Cfg.GetValue("GATEWAY_ADDR");
      if ( Str != "" )
      {
         SplitString(Str.c_str(), ',', GatewayAddrVec, true);
      }
      for ( int i = 0; i < GatewayAddrVec.size(); i++ )
      {
         HString &AddrStr = GatewayAddrVec[i];
         vector<HString> AddrPort;
         SplitString(AddrStr.c_str(), ':', AddrPort, true);
         if ( AddrPort.size() != 2 )
         {
            snprintf(ErrMsg, 2048, "Invalid gateway address(%s)", AddrStr.c_str());
            Syslog.Log(__FILE__, __LINE__, llError, "ONERR", ErrMsg);
         }
      }
      if ( !GatewayAddrVec.empty() && !HasDbFunc && Cfg.GetValueUpper("UPLOAD_STATUS") == "TRUE" )
      {
         memset(&DbFunc, 0, sizeof(DbFunc));
         DbFunc.Connect = GatewayConnect;
         DbFunc.CountStatus = GatewayCountStatus;
         DbFunc.ClearStatus = GatewayClearStatus;
         DbFunc.WriteStatus = GatewayWriteStatus;
         DbFunc.ReadStatus = GatewayReadStatus;
         DbFunc.RewriteStatus = GatewayRewriteStatus;
         HasDbFunc = true;
      }
   }
}

static bool LoadSbt()
{
   HXmlTree Sbt;
   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());

   if ( !Sbt.LoadFromFile(FileName) )
   {
      BkPrintError("Line %d : Load %s fail, %s", __LINE__, Sbt.GetErrorString().c_str());
      return false;
   }

   vector<HXmlTree*> Groups;

   Sbt.GetChildren("GROUP", Groups);

   for ( int i = 0; i < Groups.size(); i++ )
   {
      HString GroupName = Groups[i]->GetPropertyUpper("NAME");
      vector<HXmlTree*> Jobs;
      Groups[i]->GetChildren("JOB", Jobs);
      for ( int j = 0; j < Jobs.size(); j++ )
      {
         HXmlTree *Xml = Jobs[j];
         job_info Job;
         Job.FullName = GroupName + JobNameDelimiter + Xml->GetPropertyUpper("NAME");
         HString JobType = Xml->GetPropertyUpper("TYPE");
         if ( JobType != "JOB" && JobType != "EVENT_FIRE" && JobType != "EVENT_WAIT" )
         {
            continue;
         }
         HString PartitionFile = Xml->GetProperty("PARTITION_FILE");
         if ( PartitionFile != "" && strchr(PartitionFile.c_str(), '/') == NULL )
         {
            PartitionFile = ShrDir + "/part/" + PartitionFile;
         }
         Job.PartFlag = ( PartitionFile != "" );
         if ( Job.PartFlag )
         {
            FILE *f = fopen(EnvEvaluate(PartitionFile).c_str(), "rt");
            if ( f == NULL )
            {
               BkPrintError("Line %d : Open %s fail, errno = %d", __LINE__, PartitionFile.c_str(), errno);
               return false;
            }
            set<HString> PartSet;
            for ( ; ; )
            {
               char Line[500 + 1];
               char PartId[80 + 1];
               if ( ReadLine(f, Line, 500) == -1 )
               {
                  break;
               }
               BkRightTrim(Line);
               if ( Line[0] == '#' || Line[0] == '\0' )
               {
                  continue;
               }
               ReadWord(Line, PartId, " ", 80);
               PartSet.insert(PartId);
            }
            fclose(f);
            int Idx = 0;
            for ( set<HString>::iterator Iter = PartSet.begin();
                  Iter != PartSet.end(); Iter++ )
            {
               Job.PartMap[*Iter] = Idx++;
            }
         }
         JobMap[Job.FullName] = Job;
      }
   }

   return true;
}

bool Init()
{
   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      BkPrintError("Line %d : $SIMBAT_BASE not defined", __LINE__);
      return false;
   }

   char FileName[500 + 1];

   snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
   HString ConfigFile = FileName;
   snprintf(FileName, 500, "%s/tmp/config.lck", SimbatBase);
   HString ConfigLockFile = FileName;

   HXmlTree Cfg;

   {
      HFileLockRead Lock(ConfigLockFile.c_str());
      if ( !Cfg.LoadFromFile(ConfigFile.c_str()) )
      {
         BkPrintError("Line %d : Load config.xml fail, %s",
            __LINE__, Cfg.GetErrorString().c_str());
         return false;
      }
   }

   LoadFromCfg(Cfg);
   return true;
}

int UnloadFromDb()
{
   if ( !HasDbFunc )
   {
      BkPrintError("No db func");
      return 8;
   }

   if ( MyRole != nrMaster )
   {
      BkPrintError("This node is not master");
      return 8;
   }

   int r;
   char FileName[500 + 1];
   struct stat Stat;

   snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());
   r = stat(FileName, &Stat);

   if ( r == 0 )
   {
      BkPrintError("batch_status already exist");
      return 8;
   }

   char ErrStr[200 + 1];

   if ( DbFunc.Connect("", ErrStr) != 0 )
   {
      BkPrintError("Connect fail, errstr = %s", ErrStr);
      return 8;
   }

   int RecordCount;

   if ( DbFunc.CountStatus(SystemName.c_str(), &RecordCount, ErrStr) != 0 )
   {
      BkPrintError("CountStatus fail, errstr = %s", ErrStr);
      return 8;
   }

   if ( RecordCount == 0 )
   {
      return 0;
   }

   int RecLen = 0;
   char HexStr[2048 + 1];

   {
      HDbHeader Header;
      char RecType[200 + 1];
      if ( DbFunc.ReadStatus(SystemName.c_str(), 0, RecType, HexStr, ErrStr) != 0 )
      {
         BkPrintError("ReadStatus(0) fail, errstr = %s", ErrStr);
         return 8;
      }
      BkRightTrim(RecType);
      BkRightTrim(HexStr);
      RecLen = strlen(HexStr) / 2;
      BkAscToHex((char*)&Header, HexStr, RecLen);
      if ( Header.Batch.Status == bsStarted )
      {
         Header.Batch.Status = bsPaused;
      }
      {
         snprintf(FileName, 500, "%s/status/batch_status", ShrDir.c_str());
         FILE *f = fopen(FileName, "wb");
         if ( f == NULL )
         {
            BkPrintError("Can not create %s, errno = %d", FileName, errno);
            return 8;
         }
         fwrite(&Header.Batch, sizeof(Header.Batch), 1, f);
         fclose(f);
      }
      {
         snprintf(FileName, 500, "%s/status/control.dat", ShrDir.c_str());
         FILE *f = fopen(FileName, "wb");
         if ( f == NULL )
         {
            BkPrintError("Can not create %s, errno = %d", FileName, errno);
            return 8;
         }
         fwrite(&Header.Ctldat, sizeof(Header.Ctldat), 1, f);
         fclose(f);
      }
   }

   snprintf(FileName, 500, "%s/status/current.sbt", ShrDir.c_str());
   FILE *FileSbt = fopen(FileName, "wb");

   if ( FileSbt == NULL )
   {
      BkPrintError("Can not create %s, errno = %d", FileName, errno);
      return 8;
   }

   snprintf(FileName, 500, "%s/status/job_events", ShrDir.c_str());
   FILE *FileEvent = fopen(FileName, "wb");

   if ( FileEvent == NULL )
   {
      BkPrintError("Can not create %s, errno = %d", FileName, errno);
      return 8;
   }

   HXmlTree Xml;

   for ( int i = 1; i < RecordCount; i++ )
   {
      char RecType[200 + 1];
      char Buf[1000 + 1];
      if ( DbFunc.ReadStatus(SystemName.c_str(), i, RecType, HexStr, ErrStr) != 0 )
      {
         BkPrintError("ReadStatus(%d) fail, errstr = %s", i, ErrStr);
         return 8;
      }
      BkRightTrim(RecType);
      BkRightTrim(HexStr);
      if ( strcmp(RecType, "SBT") == 0 )
      {
         RecLen = strlen(HexStr) / 2;
         BkAscToHex(Buf, HexStr, RecLen);
         fwrite(Buf, RecLen, 1, FileSbt);
      }
      else if ( strcmp(RecType, "VAR") == 0 )
      {
         RecLen = strlen(HexStr) / 2;
         BkAscToHex(Buf, HexStr, RecLen);
         Buf[RecLen] = '\0';
         const char *s = strchr(Buf, '=');
         HString Name = HString(Buf, s - Buf);
         HString Value = HString(s + 1);
         Xml.SetValue(Name.c_str(), Value);
      }
      else if ( strcmp(RecType, "EVENT") == 0 )
      {
         RecLen = strlen(HexStr) / 2;
         BkAscToHex(Buf, HexStr, RecLen);
         fwrite(Buf, RecLen, 1, FileEvent);
      }
      else
      {
         BkPrintError("ReadRecord(%d), invalid type(%s)", i, RecType);
      }
   }

   fclose(FileSbt);
   fclose(FileEvent);

   {
      HString VarpoolFileName = ShrDir + "/status/var_pool";
      HFileLockWrite Lock("$SIMBAT_BASE/tmp/var_pool.lck");
      SafeSaveXmlToFile(Syslog, ErrMsg, Xml, VarpoolFileName.c_str());
   }

   return 0;
}

int ClearDb()
{
   if ( !HasDbFunc )
   {
      BkPrintError("No db func");
      return 8;
   }

   if ( MyRole != nrMaster )
   {
      BkPrintError("This node is not master");
      return 8;
   }

   char ErrStr[200 + 1];

   if ( DbFunc.Connect("", ErrStr) != 0 )
   {
      BkPrintError("Connect fail, errstr = %s", ErrStr);
      return 8;
   }

   if ( DbFunc.ClearStatus(SystemName.c_str(), ErrStr) != 0 )
   {
      BkPrintError("ClearStatus fail, errstr = %s", ErrStr);
      return 8;
   }

   return 0;
}

struct job_status
{
   HString Name;
   HString StartTime;
   HString EndTime;
   HString ElapsedTime;
   HString Status;
   HString ServerId;
   HString Tag;
};

int SummaryToSql(const char *TableName, const char *SummaryFileName, const char *ResultFileName)
{
   FILE *fin = fopen(SummaryFileName, "rt");

   if ( fin == NULL )
   {
      fprintf(stderr, "Can not open %s, errno = %d", SummaryFileName, errno);
      return 8;
   }

   FILE *fout = fopen(ResultFileName, "wt");

   if ( fout == NULL )
   {
      fprintf(stderr, "Can not create %s, errno = %d", ResultFileName, errno);
      return 8;
   }

   fprintf(fout, "drop table %s;\n", TableName);
   fprintf(fout, "create table %s\n", TableName);
   fprintf(fout, "(\n");
   fprintf(fout, "   job_name        char(80) default ' ' not null,\n");
   fprintf(fout, "   start_time      char(20) default ' ' not null,\n");
   fprintf(fout, "   end_time        char(20) default ' ' not null,\n");
   fprintf(fout, "   elapsed_time    char(20) default ' ' not null,\n");
   fprintf(fout, "   job_status      char(20) default ' ' not null,\n");
   fprintf(fout, "   server_id       char(20) default ' ' not null,\n");
   fprintf(fout, "   job_tag         char(80) default ' ' not null,\n");
   fprintf(fout, "   primary key(job_name)\n");
   fprintf(fout, ");\n");

   int LineNo = 0;
   map<HString, job_status> JobMap;

   for ( int i = 0; i < 3; i++ )
   {
      char Line[2048 + 1];
      if ( ReadLine(fin, Line, 2048) == -1 )
      {
         break;
      }
      LineNo++;
   }

   for ( ; ; )
   {
      char Line[2048 + 1];
      if ( ReadLine(fin, Line, 2048) == -1 )
      {
         break;
      }
      if ( memcmp(Line, "Batch end time", 14) == 0 )
      {
         break;
      }
      LineNo++;
      vector<HString> StrVec;
      SplitString(Line, '|', StrVec, true);
      if ( StrVec.size() < 7 )
      {
         continue;
      }
      if ( strchr(StrVec[0].c_str(), '(') != NULL )
      {
         continue;
      }
      if ( StrVec[4] != "SUCCESS" && StrVec[4] != "FAIL" && StrVec[4] != "SKIPPED" )
      {
         continue;
      }
      job_status JobStatus;
      JobStatus.Name = StrVec[0];
      JobStatus.StartTime = StrVec[1];
      JobStatus.EndTime = StrVec[2];
      JobStatus.ElapsedTime = StrVec[3];
      JobStatus.Status = StrVec[4];
      JobStatus.ServerId = StrVec[5];
      JobStatus.Tag = StrVec[6];
      if ( JobStatus.Tag == "" )
      {
         JobStatus.Tag = " ";
      }
      JobMap[JobStatus.Name] = JobStatus;
   }

   for ( map<HString, job_status>::iterator Iter = JobMap.begin();
         Iter != JobMap.end(); Iter++ )
   {
      job_status &JobStatus = Iter->second;
      fprintf(fout, "insert into %s(job_name, start_time, end_time, elapsed_time, job_status, server_id, job_tag)\n"
                    "   values('%s', '%s', '%s', '%s', '%s', '%s', '%s');\n",
         TableName,
         JobStatus.Name.c_str(),
         JobStatus.StartTime.c_str(),
         JobStatus.EndTime.c_str(),
         JobStatus.ElapsedTime.c_str(),
         JobStatus.Status.c_str(),
         JobStatus.ServerId.c_str(),
         JobStatus.Tag.c_str());
   }

   fclose(fout);
   fclose(fin);
   return 0;
}

void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH/]simbat_datafile unload_from_db\n");
   fprintf(stderr, "        [PATH/]simbat_datafile clear_db\n");
   fprintf(stderr, "        [PATH/]simbat_datafile [--table_name=table name] summary_to_sql summary_file result_file\n");
}

int main(int argc, char *argv[])
{
   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   if ( !Init() )
   {
      return 8;
   }

   int i = 1;
   HString TableName;

   for ( ; i < argc; i++ )
   {
      if ( memcmp(argv[i], "--table_name=", 13) == 0 )
      {
         TableName = argv[i] + 13;
         continue;
      }
      if ( argv[i][0] == '-' )
      {
         PrintUsage();
         return 8;
      }
      else
      {
         break;
      }
   }

   if ( i == argc )
   {
      PrintUsage();
      return 8;
   }

   if ( strcmp(argv[i], "unload_from_db") == 0 )
   {
      if ( argc != i + 1 )
      {
         PrintUsage();
         return 8;
      }
      return UnloadFromDb();
   }
   else if ( strcmp(argv[i], "clear_db") == 0 )
   {
      if ( argc != i + 1 )
      {
         PrintUsage();
         return 8;
      }
      return ClearDb();
   }
   else if ( strcmp(argv[i], "summary_to_sql") == 0 )
   {
      if ( argc != i + 3 )
      {
         PrintUsage();
         return 8;
      }
      if ( TableName == "" )
      {
         TableName = "batsts";
      }
      return SummaryToSql(TableName.c_str(), argv[i + 1], argv[i + 2]);
   }
   else
   {
      PrintUsage();
      return 8;
   }
}
